package org.moonlight.jvm;

import com.google.gson.Gson;

import org.moonlight.jvm.classfile.ClassFile;
import org.moonlight.jvm.classfile.MemberInfo;
import org.moonlight.jvm.classfile.attribute.AttributeInfo;
import org.moonlight.jvm.classpath.Classpath;
import org.moonlight.jvm.cmd.Cmd;
import org.moonlight.jvm.interpret.Interpret;
import org.moonlight.jvm.natives.java.lang.clazz.DesiredAssertionStatus0;
import org.moonlight.jvm.natives.java.lang.clazz.GetName0;
import org.moonlight.jvm.natives.java.lang.clazz.GetPrimitiveClass;
import org.moonlight.jvm.natives.java.lang.doubles.DoubleToRawLongBits;
import org.moonlight.jvm.natives.java.lang.doubles.LongBitsToDouble;
import org.moonlight.jvm.natives.java.lang.floats.FloatToRawIntBits;
import org.moonlight.jvm.natives.java.lang.floats.IntBitsToFloat;
import org.moonlight.jvm.natives.java.lang.object.Clone;
import org.moonlight.jvm.natives.java.lang.object.GetClass;
import org.moonlight.jvm.natives.java.lang.object.HashCode;
import org.moonlight.jvm.natives.java.lang.string.Intern;
import org.moonlight.jvm.natives.java.lang.system.ArrayCopy;
import org.moonlight.jvm.natives.java.lang.throwable.FillInStackTrace;
import org.moonlight.jvm.natives.sun.misc.vm.Initialize;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.ClassLoader;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;

import java.util.Arrays;

/**
 * 电子书: https://weread.qq.com/web/reader/88232cb05c99098821c4ba0k31f320d02bb31fefc0e5450
 *
 * github代码: https://github.com/zxh0/jvmgo-book/blob/a083c39ce04b700a8e3bad0136c725e40c017dc2/v1/code/go/src/jvmgo/ch09/rtda/heap/object_clone.go
 */

public class JvmApplication {

    static {
        // class
        new DesiredAssertionStatus0();
        new GetName0();
        new GetPrimitiveClass();
        // object
        new GetClass();
        new HashCode();
        new Clone();
        // double
        new DoubleToRawLongBits();
        new LongBitsToDouble();
        // float
        new FloatToRawIntBits();
        new IntBitsToFloat();
        // string
        new Intern();
        // system
        new ArrayCopy();
        // sun.misc.vm
        new Initialize();
        // java.lang.throwable
        new FillInStackTrace();
    }

    public static void main(String[] args) {
        System.out.println(~1);

        System.out.println(Arrays.toString(args));
        Cmd cmd = Cmd.parse(args);
        System.out.println(new Gson().toJson(cmd));
        if (!cmd.isOk() || cmd.isHelp()) {
            System.out.println("Usage: <main class> [-options] class [args...]");
            return;
        }
        if (cmd.isVersion()) {
            System.out.println("java version \"1.8.0\"");
            return;
        }
//        startJvm(cmd);
        new Jvm(cmd).start();
    }

    public static void startJvm(Cmd cmd) {
        // -cp D:\code\java\jvm\src\test\java Test.class arg1 arg2
        // -cp D:\code\java\jvm\src\test\java Test
        // -Xjre C:\Program Files\Java\jre1.8.0_333 -cp D:\code\java\jvm\src\test\java Test
        // -Xjre C:\Program Files\Java\jre1.8.0_333 -cp D:\code\java\jvm\src\test\java MyObject
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java Fibonacci
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java MyObject
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java ArrayTest
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java BubbleSortTest Print
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java Print
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java GetClassTest
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java StringTest
        // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java ObjectClone
        // -inst -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java BoxTest
        // -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java ParseIntTest
        Classpath cp = new Classpath(cmd.getJre(), cmd.getClasspath());

        System.out.printf("classpath:%s class:%s args:%s\n", cmd.getClasspath(), cmd.getMainClass(), cmd.getAppArgs());

        String mainClassName = cmd.getMainClass().replace(".", "/");
        try {
            ClassLoader loader = new ClassLoader(cp, cmd.isVerboseClass());
            Class mainClass = loader.loadClass(mainClassName);
            Method mainMethod = mainClass.getMainMethod();
            if (mainMethod == null) {
                throw new RuntimeException("Main method not found in class " + cmd.getMainClass());
            }
            new Interpret().interpret(mainMethod, cmd.isVerboseInst(), cmd.getArgs());
        } catch (Exception e) {
            System.out.println("Could not find or load main class " + cmd.getMainClass());
            e.printStackTrace();
        }
    }

    private static MemberInfo getMainMethod(ClassFile classFile) {
        for (MemberInfo m : classFile.getMethods()) {
            if ("main".equals(m.getName()) && "([Ljava/lang/String;)V".equals(m.getDescriptor())) {
                return m;
            }
        }
        return null;
    }

    private static ClassFile loadClass(String className, Classpath classpath) {
        try {
            byte[] classData = classpath.readClass(className);
            return new ClassFile(classData);
        } catch (Exception e) {
            System.out.println("Could not find or load main class " + className);
            e.printStackTrace();
            return null;
        }
    }

    private static void printClassInfo(ClassFile cf) {
        System.out.println("version: " + cf.getMajorVersion() + "." + cf.getMinorVersion());
        System.out.println("constants count: " + cf.getScp().getSize());
        System.out.format("access flags: 0x%x\n", cf.getAccessFlags());
        System.out.println("this class: " + cf.getClassName());
        System.out.println("super class: " + cf.getSuperClassName());
        System.out.println("interfaces: " + Arrays.toString(cf.interfaceNames()));

        System.out.println("fields count: " + cf.getFields().length);
        for (MemberInfo memberInfo : cf.getFields()) {
            System.out.format("    %s \t\t %s\n", memberInfo.getName(), memberInfo.getDescriptor());
        }

        System.out.println("methods count: " + cf.getMethods().length);
        for (MemberInfo memberInfo : cf.getMethods()) {
            System.out.format("    %s \t\t %s\n", memberInfo.getName(), memberInfo.getDescriptor());
        }

        System.out.println("attributes count: " + cf.getAttributes().length);
        for (AttributeInfo info : cf.getAttributes()) {
            System.out.format("    %s \n", info.toString());
        }
    }

}
